#!/usr/bin/python3
"""
This is a packager bot for java packages from search.maven.org
"""
import urllib
import urllib.request
from pprint import pprint
import json
import sys
import re
import datetime
import argparse
import subprocess
import os
import platform
from pathlib import Path
import hashlib
from lxml import etree
from collections import defaultdict

json_file_template = '{pkg_name}.json'
name_tag_template = 'Name:\t\t{pkg_name}'
summary_tag_template = 'Summary:\t{pkg_sum}'
version_tag_template = 'Version:\t{pkg_ver}'
release_tag_template = 'Release:\t1'
license_tag_template = 'License:\t{pkg_lic}'
home_tag_template = 'URL:\t\t{pkg_home}'
source_tag_template = 'Source0:\t{pkg_source}'

buildreq_tag_template = 'BuildRequires:\t{req}'


# TODO List
# 1. Need a reliable way to get description of module .. Partially done
# 2. requires_dist has some dependency restirction, need to present
# 3. dependency outside python (i.e. pycurl depends on libcurl) doesn't exist in pipy


class JavaPorter:
    __url_template = 'https://repo1.maven.org/maven2/{groupId}/{artifactId}/{version}/{name}-{version}.pom'
    __build_noarch = True
    __module_name = ""
    __spec_name = ""
    __pkg_name = ""
    __root = None
    __tree = None
    __ns = ""
    __plugins = ['maven-antrun-plugin','replacer']
    
    def __init__(self, args, path):
        """
        receive json from search.maven.org
        """
        pom = ""

        if path is not None:
            pom = path+"/pom.xml"
            root = etree.parse(pom).getroot() #or .parse('pom.xml') if you read it from that file
            tree = etree.ElementTree(root)
        elif args.pom != "":
            pom = args.pom+"/pom.xml"
            root = etree.parse(pom).getroot() #or .parse('pom.xml') if you read it from that file
            tree = etree.ElementTree(root)
        else:
            groupId = args.groupId
            artifactId = args.artifactId
            name = args.name
            version = args.version

            url = self.__url_template.format(groupId=groupId,artifactId=artifactId,name=name,version=version)
            with urllib.request.urlopen(url) as u:
                pom = u.read().decode('utf-8')

            strs=bytes(bytearray(pom, encoding='utf-8')) #最好是byte，不然报错
            t=etree.XML(strs) #获取根template
            tree = etree.ElementTree(t)
            root=tree.getroot()
        ns = root.nsmap[None] # 获取命名空间
        ns = "{"+ns+"}"
                
        if pom is not None:
            self.__tree = tree
            self.__root = root
            self.__ns = ns

        if self.__root is not None:
            item = self.__root.find(self.__ns+"artifactId")
            self.__module_name = item.text
            self.__spec_name = self.__module_name
            self.__pkg_name = self.__module_name
            self.__build_noarch = self.__get_buildarch()


        # if arch:
        #     self.__build_noarch = False

    def get_spec_name(self):
        return self.__spec_name

    def get_module_name(self):
        return self.__module_name

    def get_pkg_name(self):
        return self.__pkg_name

    def get_version(self):
        return self.__root.find(self.__ns+"version").text.split("-")[0]

    def get_summary(self):
        return self.__root.find(self.__ns+"description").text

    def get_home(self):
        # return self.__json["info"]["project_urls"]["Homepage"]
        return "http://www.example.com"

    def get_license(self):
        """
        By default, the license info can be achieved from json["info"]["license"]
        in rare cases it doesn't work.
        We fall back to json["info"]["classifiers"], it looks like License :: OSI Approved :: BSD Clause
        """
        req_list = ""
        license_nodes = self.__tree.xpath("//*[local-name()='license']")
        licenseInfo = defaultdict(dict)
        if license_nodes is not None:
            for license_node in license_nodes:
                infoList = []
                for child in license_node.getchildren():
                    infoList.append(child.tag.split('}')[1])
                    infoList.append(child.text)
                licenseInfo[infoList[1]].update({infoList[0]: infoList[1]})
                req_list += infoList[1]

            # for infoList in dependencyInfo:
            #     dependency = "mvn("+infoList+":"+dependencyInfo[infoList]['artifactId']+")="+ dependencyInfo[infoList]['version']
            #     req_list.append(dependency)
            
            if(req_list==""):
                return "BSD"  
            return req_list
        
        return "BSD"

    def get_source_info(self):
        """
        return a map of source filename, md5 of source, source url
        return None in errors
        """
      
        return None

    def get_source_url(self):
        """
        return URL for source file for the latest version
        return "" in errors
        """
        return "http://www.example.com/%{name}-%{version}.tar.gz"

    def __get_buildarch(self):
        """
        if this module has a prebuild package for amd64, then it is arch dependent.
        print BuildArch tag if needed.
        """
        # v = self.__json["info"]["version"]
        # version = self.__root.find(self.__ns+"version")

        # rs = self.__json["releases"][v]
        # for r in rs:
        #     if r["packagetype"] == "bdist_wheel":
        #         if r["url"].find("amd64") != -1:
        #             return False
        return True

    def is_build_noarch(self):
        return self.__build_noarch

    def get_buildarch(self):
        if (self.__build_noarch == True):
            print("BuildArch:\tnoarch")

    def get_description(self):
        return self.__root.find(self.__ns+"description").text

    def get_build_requires(self):
        req_list = []

        ## 获取普通jar依赖
        depend = self.__tree.xpath("//*[local-name()='dependency']")
        dependencyInfo = defaultdict(dict)

        for dep in depend:
            infoList = []
            for child in dep.getchildren():
                if type(child)==etree._Element:
                    infoList.append(child.tag.split('}')[1])
                    infoList.append(child.text)
            dependencyInfo[infoList[1]].update({infoList[2] : infoList[3],infoList[4] : infoList[5]})

        for infoList in dependencyInfo:
            dependency = "mvn("+infoList+":"+dependencyInfo[infoList]['artifactId']+")="+ dependencyInfo[infoList]['version']
            req_list.append(dependency)

        ## 获取父pom文件依赖
        dependencyInfo = defaultdict(dict)
        parent = self.__tree.xpath("//*[local-name()='parent']")
        for dep in parent:
            infoList = []
            for child in dep.getchildren():
                if type(child)==etree._Element:
                    infoList.append(child.tag.split('}')[1])
                    infoList.append(child.text)
            dependencyInfo[infoList[1]].update({infoList[2] : infoList[3],infoList[4] : infoList[5]})
        
        for infoList in dependencyInfo:
            dependency = "mvn("+infoList+":"+dependencyInfo[infoList]['artifactId']+":pom:"+")="+ dependencyInfo[infoList]['version']
            req_list.append(dependency)

        ## 获取maven插件依赖
        dependencyInfo = defaultdict(dict)
        plugins = self.__tree.xpath("//*[local-name()='plugin']")
        for plugin in plugins:
            infoList = []
            for child in plugin.getchildren():
                if type(child)==etree._Element:
                    infoList.append(child.tag.split('}')[1])
                    infoList.append(child.text)
            if len(infoList)==8:
                dependencyInfo[infoList[1]].update({infoList[2] : infoList[3],infoList[4] : infoList[5]})
            if len(infoList)==4 and "groupId" in infoList:
                dependencyInfo[infoList[1]].update({infoList[2] : infoList[3]})
        for infoList in dependencyInfo:
            ## 判断插件是否要显式的写入spec文件中。
            if dependencyInfo[infoList]['artifactId'] in self.__plugins:
                if len(dependencyInfo[infoList])==1:
                    dependency = "mvn("+infoList+":"+dependencyInfo[infoList]['artifactId']+")"
                else:
                    dependency = "mvn("+infoList+":"+dependencyInfo[infoList]['artifactId']+")="+ dependencyInfo[infoList]['version']
                req_list.append(dependency)

        return req_list

    def get_dependencies_rpm(self):
        rpm_list = []
        for dependency in self.get_build_requires():
            rpm = subprocess.getoutput("dnf repoquery --whatprovides "+"'"+dependency.split("=")[0]+"'")
            if len(rpm.split("\n"))==2:
                rpm_list.append(rpm.split("\n")[1])
            else:
                print("not found "+dependency+" rpm package")

        return rpm_list

    def prepare_build_requires(self):
        print(buildreq_tag_template.format(req='maven-local'))
       
    def prepare_pkg_files(self):
        if self.__build_noarch:
            print("%dir %{python3_sitelib}/*")
        else:
            print("%dir %{python3_sitearch}/*")


def download_source(porter, tgtpath):
    """
    download source file from url, and save it to target path
    """
    if not os.path.exists(tgtpath):
        print("download path %s does not exist\n", tgtpath)
        return False
    s_info = porter.get_source_info()
    if s_info is None:
        print("analyze source info error")
        return False
    s_url = s_info.get("url")
    s_path = os.path.join(tgtpath, s_info.get("filename"))
    if os.path.exists(s_path):
        with open(s_path, 'rb') as f:
            md5obj = hashlib.md5()
            md5obj.update(f.read())
            _hash = str(md5obj.hexdigest()).lower()
            if s_info.get("md5") == _hash:
                print("same source file exists, skip")
                return True
    return subprocess.call(["wget", s_url, "-P", tgtpath])

def copy_source(porter, path, tgtpath):
    """
    copy source file from path, and save it to target path
    """
    if not os.path.exists(tgtpath):
        print("copy path %s does not exist\n", tgtpath)
        return False
    name = porter.get_pkg_name() + "-" + porter.get_version() + ".tar.gz"
    subprocess.call(["tar", "-zcf", name, path])
    
    return subprocess.call(["mv", name, tgtpath])


def prepare_rpm_build_env(root):
    """
    prepare environment for rpmbuild
    """
    if not os.path.exists(root):
        print("Root path %s does not exist\n" & root)
        return ""

    buildroot = os.path.join(root, "rpmbuild")
    if not os.path.exists(buildroot):
        os.mkdir(buildroot)

    for sdir in ['SPECS', 'BUILD', 'SOURCES', 'SRPMS', 'RPMS', 'BUILDROOT']:
        bpath = os.path.join(buildroot, sdir)
        if not os.path.exists(bpath):
            os.mkdir(bpath)

    return buildroot

def package_installed(pkg):
    print(pkg)
    ret = subprocess.call(["rpm", "-qi", pkg])
    if ret == 0:
        return True

    return False


def dependencies_ready(req_list):
    """ 
    TODO: do not need to do dependency check here, do it in pyporter_run
    """
    #    if (try_pip_install_package(req) == False):
    #        return req
    return ""


def build_package(specfile):
    """
    build rpm package with rpmbuild
    """
    ret = subprocess.call(["rpmbuild", "-ba", specfile])
    return ret


def build_install_rpm(porter, path, rootpath):
    ret = build_rpm(porter, path, rootpath)
    if ret != "":
        return ret

    arch = "noarch"
    if porter.is_build_noarch():
        arch = "noarch"
    else:
        arch = platform.machine()

    pkgname = os.path.join(rootpath, "rpmbuild", "RPMS", arch, porter.get_pkg_name() + "*")
    ret = subprocess.call(["rpm", "-ivh", pkgname])
    if ret != 0:
        return "Install failed\n"

    return ""


def build_rpm(porter, path, rootpath):
    """
    full process to build rpm
    """
    buildroot = prepare_rpm_build_env(rootpath)
    if buildroot == "":
        return False

    specfile = os.path.join(buildroot, "SPECS", porter.get_spec_name() + ".spec")

    req_list = build_spec(porter, specfile)
    ret = dependencies_ready(req_list)
    if ret != "":
        print("%s can not be installed automatically, Please handle it" % ret)
        return ret

    copy_source(porter, path, os.path.join(buildroot, "SOURCES"))

    build_package(specfile)

    return ""


def build_spec(porter, output):
    """
    print out the spec file
    """
    if os.path.isdir(output):
        output = os.path.join(output, porter.get_spec_name() + ".spec")
    tmp = sys.stdout
    if output != "":
        sys.stdout = open(output, 'w+')

    print(name_tag_template.format(pkg_name=porter.get_spec_name()))
    print(version_tag_template.format(pkg_ver=porter.get_version()))
    print(release_tag_template)
    print(summary_tag_template.format(pkg_sum=porter.get_summary().replace("\n", "").strip()))
    print(license_tag_template.format(pkg_lic=porter.get_license()))
    print(home_tag_template.format(pkg_home=porter.get_home())) ## TODO URL
    print(source_tag_template.format(pkg_source=porter.get_source_url())) ## TODO SOURCE URL

    porter.prepare_build_requires()
    build_req_list = porter.get_build_requires()
    for dependency in build_req_list:
        print(buildreq_tag_template.format(req=dependency.split('=')[0]))
    print("")
    porter.get_buildarch()

    print("%description")
    print(porter.get_description())
    print("")

    print("%package  javadoc")
    print("Summary:\t" + "API documentation for %{name}")
    print("")

    print("%description javadoc")
    print(porter.get_description())
    print("")

    print("%prep")
    print("%setup -q")
    print("")
    # print("%autosetup -n %{name}-%{version}")
    # print("rm -r *gradle*")
   
    print("%build")
    print("%mvn_build -- -DskipTests")
    # print("%mvn_build -- -Dmaven.test.failure.ignore=true")
    print("")

    print("%install")
    print("%mvn_install")
    print("")
    
    print("%files -f .mfiles")
    # print("%doc README")
    # print("%doc NOTICE")
    print("%license LICENSE")
    print("%dir %{_javadir}/%{name}")
    print("")
    
    print("%files javadoc -f .mfiles-javadoc")
    print("%license LICENSE")
    # print("%doc NOTICE")
    print("")
    print("%changelog")
    date_str = datetime.date.today().strftime("%a %b %d %Y")
    print("* {today} Java_Bot <Java_Bot@openeuler.org>".format(today=date_str))
    print("- Package Spec generated")

    sys.stdout = tmp

    return build_req_list


def do_args(root):
    parser = argparse.ArgumentParser()
    parser.add_argument("mode",  type=str, help="porter mode create/upgrade")
    parser.add_argument("-g", "--groupId", help="jar groupId", type=str, default="")
    parser.add_argument("-a", "--artifactId", help="jar artifactId", type=str, default="")
    parser.add_argument("-n", "--name", help="jar name", type=str, default="")
    parser.add_argument("-v", "--version", help="jar version", type=str, default="")
    parser.add_argument("-u", "--url", help="url", type=str, default="")
    parser.add_argument("-s0", "--source0", help="jar source", type=str, default="")
    parser.add_argument("-s1", "--source1", help="jar source", type=str, default="")

    parser.add_argument("-p", "--pom", help="Create spec file by pom file", type=str, default="")
    parser.add_argument("-s", "--spec", help="spec file", type=str, default="")
    parser.add_argument("-R", "--requires", help="Get required java dependency", action="store_true")
    parser.add_argument("-b", "--build", help="Build rpm package", action="store_true")
    parser.add_argument("-B", "--buildinstall", help="Build&Install rpm package", action="store_true")
    # parser.add_argument("-r", "--rootpath", help="Build rpm package in root path", type=str, default=dft_root_path)
    parser.add_argument("-r", "--rpm", help="Get required rpm packag", action="store_true") 
    # parser.add_argument("-d", "--download", help="Download source file indicated path", action="store_true")
    # parser.add_argument("-p", "--path", help="indicated path to store files", type=str, default=os.getcwd())
    # parser.add_argument("-j", "--json", help="Get Package JSON info", action="store_true")
    parser.add_argument("-o", "--output", help="Output to file", type=str, default="")
    parser.add_argument("-t", "--type", help="Build module type : python, perl, java...", type=str, default="java")
    parser.add_argument("-arch", "--arch", help="Build module with arch", action="store_true")
    # parser.add_argument("pkg", type=str, help="The Java Package Name")

    return parser


def porter_creator(args):
    if args.type == "java":
        return JavaPorter(args,None)

    return None
    

def get_requires(path):
    if path is not None:
        return JavaPorter(None,path)

    return None